/*!
FileReader.js - v0.99
A lightweight wrapper for common FileReader usage.
Copyright 2014 Brian Grinstead - MIT License.
See http://github.com/bgrins/filereader.js for documentation.
*/

;(function (window, document) {
  var FileReader = window.FileReader
  var FileReaderSyncSupport = false
  var workerScript =
    "self.addEventListener('message', function(e) { var data=e.data; try { var reader = new FileReaderSync; postMessage({ result: reader[data.readAs](data.file), extra: data.extra, file: data.file})} catch(e){ postMessage({ result:'error', extra:data.extra, file:data.file}); } }, false);"
  var syncDetectionScript =
    "onmessage = function(e) { postMessage(!!FileReaderSync); };"
  var fileReaderEvents = [
    "loadstart",
    "progress",
    "load",
    "abort",
    "error",
    "loadend",
  ]
  var sync = false
  var FileReaderJS = (window.FileReaderJS = {
    enabled: false,
    setupInput: setupInput,
    setupBlob: setupBlob,
    setupDrop: setupDrop,
    setupClipboard: setupClipboard,
    setSync: function (value) {
      sync = value

      if (sync && !FileReaderSyncSupport) {
        checkFileReaderSyncSupport()
      }
    },
    getSync: function () {
      return sync && FileReaderSyncSupport
    },
    output: [],
    opts: {
      dragClass: "drag",
      accept: false,
      readAsDefault: "DataURL",
      readAsMap: {},
      on: {
        loadstart: noop,
        progress: noop,
        load: noop,
        abort: noop,
        error: noop,
        loadend: noop,
        skip: noop,
        groupstart: noop,
        groupend: noop,
        beforestart: noop,
      },
    },
  })

  // Setup jQuery plugin (if available)
  if (typeof jQuery !== "undefined") {
    jQuery.fn.fileReaderJS = function (opts) {
      return this.each(function () {
        if (jQuery(this).is("input")) {
          setupInput(this, opts)
        } else {
          setupDrop(this, opts)
        }
      })
    }

    jQuery.fn.fileClipboard = function (opts) {
      return this.each(function () {
        setupClipboard(this, opts)
      })
    }
  }

  // Not all browsers support the FileReader interface. Return with the enabled bit = false.
  if (!FileReader) {
    return
  }

  // makeWorker is a little wrapper for generating web workers from strings
  function makeWorker(script) {
    var URL = window.URL || window.webkitURL
    var Blob = window.Blob
    var Worker = window.Worker

    if (!URL || !Blob || !Worker || !script) {
      return null
    }

    var blob = new Blob([script])
    var worker = new Worker(URL.createObjectURL(blob))
    return worker
  }

  // setupClipboard: bind to clipboard events (intended for document.body)
  function setupClipboard(element, opts) {
    if (!FileReaderJS.enabled) {
      return
    }
    var instanceOptions = extend(extend({}, FileReaderJS.opts), opts)

    element.addEventListener("paste", onpaste, false)

    function onpaste(e) {
      var files = []
      var clipboardData = e.clipboardData || {}
      var items = clipboardData.items || []

      for (var i = 0; i < items.length; i++) {
        var file = items[i].getAsFile()

        if (file) {
          // Create a fake file name for images from clipboard, since this data doesn't get sent
          var matches = new RegExp("/(.*)").exec(file.type)
          if (!file.name && matches) {
            var extension = matches[1]
            file.name = "clipboard" + i + "." + extension
          }

          files.push(file)
        }
      }

      if (files.length) {
        processFileList(e, files, instanceOptions)
        e.preventDefault()
        e.stopPropagation()
      }
    }
  }

  // setupInput: bind the 'change' event to an input[type=file]
  function setupInput(input, opts) {
    if (!FileReaderJS.enabled) {
      return
    }
    var instanceOptions = extend(extend({}, FileReaderJS.opts), opts)

    input.addEventListener("change", inputChange, false)
    input.addEventListener("drop", inputDrop, false)

    function inputChange(e) {
      processFileList(e, input.files, instanceOptions)
    }

    function inputDrop(e) {
      e.stopPropagation()
      e.preventDefault()
      processFileList(e, e.dataTransfer.files, instanceOptions)
    }
  }
  // setupFile: bind the 'change' event to an input[type=file]
  function setupBlob(blob, opts) {
    if (!FileReaderJS.enabled) {
      return
    }

    if (blob.constructor !== Array && blob.constructor !== Function) {
      if (blob.name === undefined) {
        blob.name = "blob"
      }
      blob = [blob]
    } else {
      if (blob[0].name === undefined) {
        blob[0].name = "blob"
      }
    }

    var instanceOptions = extend(extend({}, FileReaderJS.opts), opts)

    processFileList(null, blob, instanceOptions)
  }
  // setupDrop: bind the 'drop' event for a DOM element
  function setupDrop(dropbox, opts) {
    if (!FileReaderJS.enabled) {
      return
    }
    var instanceOptions = extend(extend({}, FileReaderJS.opts), opts)
    var dragClass = instanceOptions.dragClass
    var initializedOnBody = false

    // Bind drag events to the dropbox to add the class while dragging, and accept the drop data transfer.
    dropbox.addEventListener("dragenter", onlyWithFiles(dragenter), false)
    dropbox.addEventListener("dragleave", onlyWithFiles(dragleave), false)
    dropbox.addEventListener("dragover", onlyWithFiles(dragover), false)
    dropbox.addEventListener("drop", onlyWithFiles(drop), false)

    // Bind to body to prevent the dropbox events from firing when it was initialized on the page.
    document.body.addEventListener("dragstart", bodydragstart, true)
    document.body.addEventListener("dragend", bodydragend, true)
    document.body.addEventListener("drop", bodydrop, false)

    function bodydragend(e) {
      initializedOnBody = false
    }

    function bodydragstart(e) {
      initializedOnBody = true
    }

    function bodydrop(e) {
      if (e.dataTransfer.files && e.dataTransfer.files.length) {
        e.stopPropagation()
        e.preventDefault()
      }
    }

    function onlyWithFiles(fn) {
      return function () {
        if (!initializedOnBody) {
          fn.apply(this, arguments)
        }
      }
    }

    function drop(e) {
      e.stopPropagation()
      e.preventDefault()
      if (dragClass) {
        removeClass(dropbox, dragClass)
      }
      processFileList(e, e.dataTransfer.files, instanceOptions)
    }

    function dragenter(e) {
      e.stopPropagation()
      e.preventDefault()
      if (dragClass) {
        addClass(dropbox, dragClass)
      }
    }

    function dragleave(e) {
      if (dragClass) {
        removeClass(dropbox, dragClass)
      }
    }

    function dragover(e) {
      e.stopPropagation()
      e.preventDefault()
      if (dragClass) {
        addClass(dropbox, dragClass)
      }
    }
  }

  // setupCustomFileProperties: modify the file object with extra properties
  function setupCustomFileProperties(files, groupID) {
    for (var i = 0; i < files.length; i++) {
      var file = files[i]
      file.extra = {
        nameNoExtension: file.name.substring(0, file.name.lastIndexOf(".")),
        extension: file.name.substring(file.name.lastIndexOf(".") + 1),
        fileID: i,
        uniqueID: getUniqueID(),
        groupID: groupID,
        prettySize: prettySize(file.size),
      }
    }
  }

  // getReadAsMethod: return method name for 'readAs*' - http://www.w3.org/TR/FileAPI/#reading-a-file
  function getReadAsMethod(type, readAsMap, readAsDefault) {
    for (var r in readAsMap) {
      if (type.match(new RegExp(r))) {
        return "readAs" + readAsMap[r]
      }
    }
    return "readAs" + readAsDefault
  }

  // processFileList: read the files with FileReader, send off custom events.
  function processFileList(e, files, opts) {
    var filesLeft = files.length
    var group = {
      groupID: getGroupID(),
      files: files,
      started: new Date(),
    }

    function groupEnd() {
      group.ended = new Date()
      opts.on.groupend(group)
    }

    function groupFileDone() {
      if (--filesLeft === 0) {
        groupEnd()
      }
    }

    FileReaderJS.output.push(group)
    setupCustomFileProperties(files, group.groupID)

    opts.on.groupstart(group)

    // No files in group - end immediately
    if (!files.length) {
      groupEnd()
      return
    }

    var supportsSync = sync && FileReaderSyncSupport
    var syncWorker

    // Only initialize the synchronous worker if the option is enabled - to prevent the overhead
    if (supportsSync) {
      syncWorker = makeWorker(workerScript)
      syncWorker.onmessage = function (e) {
        var file = e.data.file
        var result = e.data.result

        // Workers seem to lose the custom property on the file object.
        if (!file.extra) {
          file.extra = e.data.extra
        }

        file.extra.ended = new Date()

        // Call error or load event depending on success of the read from the worker.
        opts.on[result === "error" ? "error" : "load"](
          { target: { result: result } },
          file,
        )
        groupFileDone()
      }
    }

    Array.prototype.forEach.call(files, function (file) {
      file.extra.started = new Date()

      if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
        opts.on.skip(file)
        groupFileDone()
        return
      }

      if (opts.on.beforestart(file) === false) {
        opts.on.skip(file)
        groupFileDone()
        return
      }

      var readAs = getReadAsMethod(
        file.type,
        opts.readAsMap,
        opts.readAsDefault,
      )

      if (syncWorker) {
        syncWorker.postMessage({
          file: file,
          extra: file.extra,
          readAs: readAs,
        })
      } else {
        var reader = new FileReader()
        reader.originalEvent = e

        fileReaderEvents.forEach(function (eventName) {
          reader["on" + eventName] = function (e) {
            if (eventName == "load" || eventName == "error") {
              file.extra.ended = new Date()
            }
            opts.on[eventName](e, file)
            if (eventName == "loadend") {
              groupFileDone()
            }
          }
        })
        reader[readAs](file)
      }
    })
  }

  // checkFileReaderSyncSupport: Create a temporary worker and see if FileReaderSync exists
  function checkFileReaderSyncSupport() {
    var worker = makeWorker(syncDetectionScript)
    if (worker) {
      worker.onmessage = function (e) {
        FileReaderSyncSupport = e.data
      }
      worker.postMessage({})
    }
  }

  // noop: do nothing
  function noop() {}

  // extend: used to make deep copies of options object
  function extend(destination, source) {
    for (var property in source) {
      if (
        source[property] &&
        source[property].constructor &&
        source[property].constructor === Object
      ) {
        destination[property] = destination[property] || {}
        arguments.callee(destination[property], source[property])
      } else {
        destination[property] = source[property]
      }
    }
    return destination
  }

  // hasClass: does an element have the css class?
  function hasClass(el, name) {
    return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className)
  }

  // addClass: add the css class for the element.
  function addClass(el, name) {
    if (!hasClass(el, name)) {
      el.className = el.className ? [el.className, name].join(" ") : name
    }
  }

  // removeClass: remove the css class from the element.
  function removeClass(el, name) {
    if (hasClass(el, name)) {
      var c = el.className
      el.className = c
        .replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ")
        .replace(/^\s\s*/, "")
        .replace(/\s\s*$/, "")
    }
  }

  // prettySize: convert bytes to a more readable string.
  function prettySize(bytes) {
    var s = ["bytes", "kb", "MB", "GB", "TB", "PB"]
    var e = Math.floor(Math.log(bytes) / Math.log(1024))
    return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + " " + s[e]
  }

  // getGroupID: generate a unique int ID for groups.
  var getGroupID = (function (id) {
    return function () {
      return id++
    }
  })(0)

  // getUniqueID: generate a unique int ID for files
  var getUniqueID = (function (id) {
    return function () {
      return id++
    }
  })(0)

  // The interface is supported, bind the FileReaderJS callbacks
  FileReaderJS.enabled = true
})(this, document)
